using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Server
{
	internal class ClientConnection
	{
		const int cBufferSize = 1024;
		byte[] mBuffer;
		Socket mSocket;
		IPEndPoint mEndPoint;

		public ClientConnection(Socket socket)
		{
			mSocket = socket;
			mEndPoint = (IPEndPoint)socket.RemoteEndPoint;
			mBuffer = new byte[cBufferSize];
		}
		public Socket Socket
		{
			get { return mSocket; }
		}
		public IPEndPoint EndPoint
		{
			get { return mEndPoint; }
		}
		public byte[] Buffer
		{
			get { return mBuffer; }
		}
	}
	public class TcpDemon : IDemon
	{
		#region Implementation detail:
		readonly int mPort;
		readonly int mCapacity;
		ClientEvent mOnConnected = null;
		ClientEvent mOnDisconnected = null;
		ClientDataEvent mOnData = null;
		ClientErrorEvent mOnError = null;
		Socket mListener = null;
		Hashtable mClientConnections= new Hashtable(); 
		bool mRunning= false;

		void RaiseDisconnectedEvent(ClientConnection connection)
		{
			if (null != connection) mClientConnections.Remove(connection.EndPoint);
			if ((null != mOnDisconnected) && (null != connection)) mOnDisconnected(connection.EndPoint);
		}
		void RaiseErrorEvent(ClientConnection connection, SocketException error)
		{
			if ((null != mOnError) && (null != connection)) mOnError(connection.EndPoint, error);
		}
		void HandleConnectionData(ClientConnection connection, IAsyncResult parameter)
		{
			int read = connection.Socket.EndReceive(parameter);
			if (0 == read) RaiseDisconnectedEvent(connection);
			else
			{
				byte[] received = new byte[read];
				Array.Copy(connection.Buffer, 0, received, 0, read);
				if (null != mOnData) mOnData(connection.EndPoint, received);
				StartWaitingForData(connection);
			}
		}
		void HandleIncomingData(IAsyncResult parameter)
		{
			ClientConnection connection = (ClientConnection) parameter.AsyncState;
			try
			{
				HandleConnectionData(connection, parameter);
			}
			catch (ObjectDisposedException)
			{
				RaiseDisconnectedEvent(connection);
			}
			catch (SocketException x)
			{
				RaiseErrorEvent(connection, x);
			}
		}
		void StartWaitingForData(ClientConnection connection)
		{
			connection.Socket.BeginReceive(connection.Buffer, 
				0, 
				connection.Buffer.Length, 
				SocketFlags.None, 
				new AsyncCallback(HandleIncomingData), 
				connection);
		}
		ClientConnection CreateClientConnection(IAsyncResult parameter)
		{
			Socket socket = mListener.EndAccept(parameter);
			ClientConnection result = new ClientConnection(socket);
			mClientConnections.Add(result.EndPoint, result);
			if (null != mOnConnected) mOnConnected(result.EndPoint);
			StartWaitingForData(result);
			WaitForClient();
			return result;
		}
		void HandleConnection(IAsyncResult parameter)
		{
			ClientConnection connection = null;
			try
			{
				connection= CreateClientConnection(parameter);
			}
			catch (ObjectDisposedException)
			{
				RaiseDisconnectedEvent(connection);
			}
			catch (SocketException x)
			{
				RaiseErrorEvent(connection, x);
			}
		}
		void WaitForClient()
		{
			mListener.BeginAccept(new AsyncCallback(HandleConnection), null);
		}
		void HandleSendFinished(IAsyncResult parameter)
		{
			ClientConnection connection = (ClientConnection) parameter.AsyncState;
			connection.Socket.EndSend(parameter);
		}

		#endregion
		#region Public interface:
		public TcpDemon(int port, int capacity)
		{
			mPort = port;
			mCapacity= capacity;
		}
		#endregion
		#region Implementing IDemon:
		public void Start()
		{
			mRunning= true;
			mListener= new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			mListener.Bind(new IPEndPoint(IPAddress.Any, mPort));
			mListener.Listen(mCapacity);
			WaitForClient();
		}
		public void Stop()
		{
			mListener.Close();
			foreach (DictionaryEntry entry in mClientConnections)
			{
				ClientConnection client= (ClientConnection) entry.Value;
				client.Socket.Close();
			}
			mClientConnections.Clear();
			mRunning= false;
		}
		public void Send(IPEndPoint client, byte[] buffer)
		{
			if (!mClientConnections.ContainsKey(client)) throw new ArgumentException("Client not connected.", "client");
			ClientConnection connection = (ClientConnection) mClientConnections[client];
			try
			{
				connection.Socket.BeginSend(buffer, 
					0, 
					buffer.Length, 
					SocketFlags.None, 
					new AsyncCallback(HandleSendFinished), 
					connection);
			}
			catch (ObjectDisposedException)
			{
				RaiseDisconnectedEvent(connection);
			}
			catch (SocketException x)
			{
				RaiseErrorEvent(connection, x);
			}
		}
		public ClientEvent OnConnected
		{
			get { return mOnConnected; }
			set 
			{ 
				if (mRunning) throw new InvalidOperationException("Must not set OnConnected while listening.");
				mOnConnected= value; 
			}
		}
		public ClientEvent OnDisconnected
		{
			get { return mOnDisconnected; }
			set 
			{ 
				if (mRunning) throw new InvalidOperationException("Must not set OnDisconnected while listening.");
				mOnDisconnected= value; 
			}
		}
		public ClientDataEvent OnData
		{
			get { return mOnData; }
			set 
			{ 
				if (mRunning) throw new InvalidOperationException("Must not set OnData while listening.");
				mOnData= value; 
			}
		}
		public ClientErrorEvent OnError
		{
			get { return mOnError; }
			set 
			{ 
				if (mRunning) throw new InvalidOperationException("Must not set OnError while listening.");
				mOnError= value; 
			}
		}
        
		#endregion
	}
}

